package lumis.portal.serialization;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import lumis.portal.serialization.SerializationSettings.CollectionAliasSettings;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
import com.thoughtworks.xstream.io.xml.XppDriver;

/**
 * Utility class for use with the XStream serialization framework
 *
 * @version $Revision: 11978 $ $Date: 2010-10-04 12:54:20 -0300 (Mon, 04 Oct 2010) $
 * @since 4.0.7
 */
class XStreamUtil implements ISerializer
{
	private final XStream xstream;
	
	private static final XppDriver xppDriver = new XppDriver();

	public XStreamUtil() throws SerializationException
	{
		this(null);
	}
	
	public XStreamUtil(SerializationSettings serializationSettings) throws SerializationException
	{
		super();
		
		String vendor = System.getProperty("java.vm.vendor");
		if (vendor.toLowerCase().indexOf("oracle") != -1)
		{
			// force Sun14ReflectionProvider for Oracle vendor
			// (XStream 1.3 does not correctly detect Oracle vendor) 
			xstream = new XStream(new Sun14ReflectionProvider(), xppDriver);
		}
		else
		{
			xstream = new XStream(xppDriver);
		}
		
		if (serializationSettings != null)
		{
			// set aliases
			for (Class<?> c : serializationSettings.getClassAliases().keySet())
				try
				{
					xstream.alias(serializationSettings.getClassAliases().get(c), c);
				}
				catch (Exception e)
				{
					throw new SerializationException("STR_SPECIFIED_CLASS_FOR_CLASS_ALIAS_NOT FOUND;" + c.getName(), e);
				}
			
			// register converters
			for (Class<?> c : serializationSettings.getConverters())
				try
				{
					xstream.registerConverter((Converter)c.newInstance());
				}
				catch (Exception e)
				{
					throw new SerializationException("STR_SPECIFIED_CLASS_FOR_CONVERTER_NOT FOUND;" + c.getName(), e);
				}
			
			// omit transient fields
			for (Class<?> c : serializationSettings.getTransientFields().keySet())
			{
				for (String fieldName : serializationSettings.getTransientFields().get(c))
				{
					try
					{
						xstream.omitField(c,fieldName);
					}
					catch (Exception e)
					{
						throw new SerializationException("STR_SPECIFIED_CLASS_FOR_OMITTING_FIELD_NOT FOUND;" + c.getName(), e);
					}
				}
			}
			
			// omit collection fields
			for (Class<?> c : serializationSettings.getOmittedCollections().keySet())
			{
				for (CollectionAliasSettings collectionAliasSettings : serializationSettings.getOmittedCollections().get(c))
				{
					try
					{
						xstream.addImplicitCollection(c, collectionAliasSettings.getFieldName(), collectionAliasSettings.getItemAlias(), collectionAliasSettings.getItemClass());
						//xstream.addImplicitCollection(ownerType, fieldName, itemFieldName, itemType)
					}
					catch (Exception e)
					{
						throw new SerializationException("STR_SPECIFIED_CLASS_FOR_OMITTING_COLLECTION_NOT FOUND;" + c.getName(), e);
					}
				}
			}
	
			// set field aliases
			for (Class<?> c : serializationSettings.getFieldAliases().keySet())
			{
				for (String field : serializationSettings.getFieldAliases().get(c).keySet())
				{
					try
					{
						xstream.aliasField(serializationSettings.getFieldAliases().get(c).get(field), c, field);
					}
					catch (Exception e)
					{
						throw new SerializationException("STR_SPECIFIED_CLASS_FOR_FIELD_ALIAS_NOT FOUND;" + c.getName(), e);
					}
				}
			}
		}
	
	}

	public String toXmlString(Object o) throws SerializationException
	{
		return toXmlString(o, null);
	}
	
	public String toXmlString(Object o, String alias) throws SerializationException
	{
        Writer writer = new StringWriter();
        toXml(o, alias, writer);
		return writer.toString();
	}
	
	public void toXmlFile(Object o, String filePath) throws SerializationException
	{
		toXmlFile(o, filePath, null);
	}
	
	public void toOutputStream(Object o, OutputStream outputStream, String alias) throws SerializationException
	{
		try
		{
			OutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
			try
			{
				OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bufferedOutputStream, "UTF-8");
				try
				{
					toXml(o, alias, outputStreamWriter);
				}
				finally
				{
					outputStreamWriter.close();
				}
			}
			finally
			{
				bufferedOutputStream.close();
			}
		}
		catch (IOException e)
		{
			throw new SerializationException("STR_ERROR_SERIALIZING_OBJECT", e);
		}
	}
	public void toXmlFile(Object o, String filePath, String alias) throws SerializationException
	{
		File file = new File(filePath);
		File parentFile = file.getParentFile();
		parentFile.mkdirs();

		try
		{
			toOutputStream(o, new FileOutputStream(file), alias);
		}
		catch (FileNotFoundException e)
		{
			throw new SerializationException("STR_ERROR_SERIALIZING_OBJECT", e);
		}
	}

	private void toXml(Object o, String alias, Writer writer) throws SerializationException
	{
		if (alias != null)
			xstream.alias(alias, o.getClass());
		xstream.toXML(o, writer);
	}
	
	public <T> T fromXmlFile(Class<T> objectClass, String alias, String filePath) throws SerializationException
	{
		return fromXmlFile(objectClass, alias, filePath, false);
	}

	@SuppressWarnings("unchecked")
	public <T> T fromInputStream(Class<T> objectClass, String alias, InputStream inputStream) throws SerializationException
	{
		if (alias != null)
			xstream.alias(alias, objectClass);

		InputStreamReader inputStreamReader = null;
		try
		{
			InputStream bufferedInputStream = new BufferedInputStream(inputStream);
			inputStreamReader = new InputStreamReader(bufferedInputStream, "UTF-8");

			Object o = xstream.fromXML(inputStreamReader);
			return (T)o;
		}
		catch (UnsupportedEncodingException e)
		{
			throw new IllegalStateException("Error deserializing object: UTF-8 encoding not supported", e);
		}
		finally
		{
			try
			{
				if (inputStreamReader != null)
					inputStreamReader.close();
			}
			catch (IOException e)
			{
				throw new IllegalStateException("Error closing deserialization stream reader", e);
			}
		}
	}
	
	public <T> T fromXmlFile(Class<T> objectClass, String alias, String filePath, boolean fileIsOptional) throws SerializationException
	{
		if (alias != null)
			xstream.alias(alias, objectClass);

		File file = new File(filePath);

		try
		{
			InputStream fileInputStream = new FileInputStream(file);
			return fromInputStream(objectClass, alias, fileInputStream);
		}
		catch (FileNotFoundException e)
		{
			if (fileIsOptional)
				return null;
			throw new SerializationException("STR_ERROR_DESERIALIZING_OBJECT", e);
		}
	}

}
